#include "stdafx.h"
#include "RenderParticlesForward.h"

#include "Renderer.h"
#include "LoadShader.h"
#include "InputLayouts.h"
#include "RenderingData.h"
#include "RenderingSettings.h"
#include "CSM.h"

using namespace mgfx;

ShaderID ShaderRenderParticlesForward;
VertexBufferID vertexBuffer;
IndexBufferID indexBuffer;

void mgfx::initRenderParticlesForward()
{
	//std::stringstream ss;
	//std::vector<ShaderDefine> defines;
	//defines.push_back(ShaderDefine("SHADOWS", "1"));
	//ss.str("");
	//ss << CSM::CASCADE_MAP_SIZE;
	//defines.push_back(ShaderDefine("CASCADE_MAP_SIZE", ss.str()));
	//shaderParticles = loadShaderFromFile("ParticleForward.fx", defines);
	ShaderRenderParticlesForward = loadShaderFromFile("ParticleForward.fx");

	Renderer * rdr = Renderer::Instance;

	vertexBuffer = rdr->addVertexBuffer(NULL, ParticleSystem::MAX_PARTICLES * 4, sizeof(VertexParticle), true);

	short * indices = new short[ParticleSystem::MAX_PARTICLES * 6];
	for (uint i = 0; i < ParticleSystem::MAX_PARTICLES; i++)
	{
		indices[i*6 + 0] = i*4 + 0;
		indices[i*6 + 1] = i*4 + 1;
		indices[i*6 + 2] = i*4 + 2;

		indices[i*6 + 3] = i*4 + 0;
		indices[i*6 + 4] = i*4 + 2;
		indices[i*6 + 5] = i*4 + 3;
	}
	indexBuffer = rdr->addIndexBuffer(indices, ParticleSystem::MAX_PARTICLES * 6, sizeof(short), false);
	delete [] indices;
}

#define MAX_POINT_LIGHTS 128

struct PointLightData
{
	XMFLOAT4 PosAndRadius;
	XMFLOAT4 ColorAndIntens;
} pointLightArray[MAX_POINT_LIGHTS];


static void setPointLightData(const RenderingData & data)
{
	assert(data.PointLights.NumElements <= MAX_POINT_LIGHTS);

	Renderer * rdr = Renderer::Instance;

	for(uint i = 0; i < data.PointLights.NumElements; i++)
	{
		const PointLight & light = data.PointLights[i];
		pointLightArray[i].PosAndRadius = XMFLOAT4(light.Position.x, light.Position.y, light.Position.z, light.Radius);
		pointLightArray[i].ColorAndIntens = XMFLOAT4(light.Color.x, light.Color.y, light.Color.z, light.Intensity);
	}

	rdr->setShaderRawValue("gPointLights", pointLightArray, sizeof(PointLightData) * data.PointLights.NumElements);
	rdr->setShaderInt("gNumPointLights", (int)data.PointLights.NumElements);
}

#define MAX_SPOT_LIGHTS 128

struct SpotLightData
{
	XMFLOAT4 PosAndRadius;
	XMFLOAT4 ColorAndIntens;
	XMFLOAT4 DirAndCosAngle;
} spotLightArray[MAX_SPOT_LIGHTS];

#define MAX_SHD_SPOT_LIGHTS 16

struct ShdSpotLightData
{
	XMFLOAT4 PosAndRadius;
	XMFLOAT4 ColorAndIntens;
	XMFLOAT4 DirAndCosAngle;
	XMFLOAT4X4 ViewProj;
} shdSpotLightArray[MAX_SHD_SPOT_LIGHTS];

TextureID spotShadowMapIDs[MAX_SHD_SPOT_LIGHTS];

static void setSpotLightData(const RenderingData & data)
{
	Renderer * rdr = Renderer::Instance;

	uint numSpotLights = 0, numShdSpotLights = 0;

	for(uint i = 0; i < data.SpotLights.NumElements; i++)
	{
		const SpotLight & light = data.SpotLights[i];
		XMFLOAT4 posAndRadius = XMFLOAT4(light.Position.x, light.Position.y, light.Position.z, light.Radius);
		XMFLOAT4 colorAndIntens = XMFLOAT4(light.Color.x, light.Color.y, light.Color.z, light.Intensity);

		XMVECTOR spotDir = XMVector3TransformNormal(
			SpotLight::InitialDirection,
			XMMatrixRotationQuaternion( XMLoadFloat4(&light.Orientation) )
		);
		XMFLOAT4 dirAndCosAngle;
		XMStoreFloat4(&dirAndCosAngle, spotDir);
		dirAndCosAngle.w = light.SpotCosAngle;

		if(light.IsCastingShadows)
		{
			uint idx = numShdSpotLights;
			shdSpotLightArray[idx].PosAndRadius = posAndRadius;
			shdSpotLightArray[idx].ColorAndIntens = colorAndIntens;
			shdSpotLightArray[idx].DirAndCosAngle = dirAndCosAngle;

			XMVECTOR tmp;
			XMMATRIX viewProj = XMMatrixMultiply(
				XMMatrixInverse(&tmp, XMLoadFloat4x4(&light.WorldTransform)),
				XMLoadFloat4x4(&light.ProjectionMatrix)
			);
			// apparently when you send matrix to shader as a raw data instead of as a matrix (or matrix array) variable
			// you need to manually transpose it
			viewProj = XMMatrixTranspose(viewProj);
			XMStoreFloat4x4(&shdSpotLightArray[idx].ViewProj, viewProj);

			spotShadowMapIDs[idx] = light.ShadowMap;

			numShdSpotLights++;

			assert(numShdSpotLights <= MAX_SHD_SPOT_LIGHTS);
		}
		else
		{
			uint idx = numSpotLights;
			spotLightArray[idx].PosAndRadius = posAndRadius;
			spotLightArray[idx].ColorAndIntens = colorAndIntens;
			spotLightArray[idx].DirAndCosAngle = dirAndCosAngle;

			numSpotLights++;

			assert(numSpotLights <= MAX_SPOT_LIGHTS);
		}
	}

	if(numSpotLights > 0)
		rdr->setShaderRawValue("gSpotLights", spotLightArray, sizeof(SpotLightData) * numSpotLights);
	rdr->setShaderInt("gNumSpotLights", (int)numSpotLights);

	if(numShdSpotLights > 0)
	{
		rdr->setShaderRawValue("gShdSpotLights", shdSpotLightArray, sizeof(ShdSpotLightData) * numShdSpotLights);
		rdr->setShaderResourceArray("gSpotShadowMaps", spotShadowMapIDs, numShdSpotLights);
	}
	rdr->setShaderInt("gNumShdSpotLights", (int)numShdSpotLights);
}

void mgfx::renderParticlesForward(const RenderingData & data, const RenderingSettings & settings, TextureID dstBuffer, TextureID depthBuffer, const CSM & csm)
{
	if(settings.RenderParticlesCS)
		return;
	Renderer * rdr = Renderer::Instance;

	rdr->setPrimitiveType(TRIANGLE_LIST);
	rdr->setVertexFormat(VertexParticle::VertexFormat);
	rdr->setRenderTargets(1, &dstBuffer, depthBuffer);
	rdr->setIndexBuffer(indexBuffer);

	rdr->setShader(ShaderRenderParticlesForward);

	XMMATRIX view = XMLoadFloat4x4(&data.ViewMatrix), proj = XMLoadFloat4x4(&data.ProjMatrix);
	rdr->setShaderMatrix("gViewMatrix", (float*)&view);
	rdr->setShaderMatrix("gProjMatrix", (float*)&proj);
	rdr->setShaderFloatVector("gEyePos", (float*)&data.EyePosition);
	

	rdr->setShaderMatrixArray("gLightViewProjs", (float*)&csm.Matrices, CSM::NUM_CASCADES);
	rdr->setShaderFloatVector("gCascadeDepths", (float*)&csm.SplitDepths[1]);
	rdr->setShaderResource("gCSMShadowMap", csm.AllCascadesMap);

	for(uint i = 0; i < data.PartSystems.NumElements; i++)
	{
		const ParticleSystem & ps = data.PartSystems[i];
		
		memcpy(rdr->mapVertexBuffer(vertexBuffer), ps.Vertices, ps.NumParticlesActive * 4 * sizeof(VertexParticle));
		rdr->unmapVertexBuffer(vertexBuffer);

		rdr->setVertexBuffer(vertexBuffer);

		XMMATRIX world = XMLoadFloat4x4(&ps.WorldTransform);
		XMMATRIX worldView = XMMatrixMultiply(world , view );

		rdr->setShaderMatrix("gWorldMatrix", (float*)&world);
		rdr->setShaderMatrix("gWorldViewMatrix", (float*)&worldView);
		rdr->setShaderResource("gDiffuseMap", ps.Desc.Texture);

		const DirectionalLight & light = data.MainLight;
		float dirLightColorAndIntens[] = { light.Color.x, light.Color.y, light.Color.z, light.Intensity };
		rdr->setShaderFloatVector("gLightColorAndIntens", dirLightColorAndIntens);
		rdr->setShaderFloatVector("gLightDirW", (float*)&light.Direction);

		setPointLightData(data);
		setSpotLightData(data);

		rdr->drawIndexed(ps.NumParticlesActive * 6);
	}

	rdr->setShaderResource("gCSMShadowMap", Renderer::TEXTURE_NONE);
	TextureID zeroTex[MAX_SHD_SPOT_LIGHTS] = { Renderer::TEXTURE_NONE };
	rdr->setShaderResourceArray("gSpotShadowMaps", zeroTex, MAX_SHD_SPOT_LIGHTS);
	rdr->applyConstantsWithoutDrawing();
}
